﻿using System;
using System.Collections.Generic;
using System.Threading;

namespace Encloser.Toolkit
{
    public class BlockedQueue<T>
    {
        private readonly List<T> list;
        private readonly int capacity;
        private readonly int period;
        private readonly Action fullCallback;
        private readonly Action emptyCallback;
        private bool fullFlag = false;
        private bool emptyFlag = false;

        public BlockedQueue(int capacity = int.MaxValue, int period = 10, Action fullCallback = null, Action emptyCallback = null)
        {
            this.capacity = capacity < 1 ? int.MaxValue : capacity;
            this.period = period < 1 || period > 3600 ? 10 : period;
            this.fullCallback = fullCallback;
            this.emptyCallback = emptyCallback;
            list = new List<T>(capacity);
        }

        public void Reset()
        {
            Monitor.Enter(list);
            fullFlag = emptyFlag = false;
            list.Clear();
            Monitor.Exit(list);
        }

        public void Add(T item, Func<bool> quitHook = null)
        {
            if (quitHook != null && quitHook()) return;
            Monitor.Enter(list);
            while (list.Count >= capacity)
            {
                if (quitHook != null && quitHook())
                {
                    Monitor.Exit(list);
                    return;
                }
                if (!fullFlag)
                {
                    fullFlag = true;
                    fullCallback?.Invoke();
                }
                Monitor.Wait(list, period);
            }
            list.Add(item);
            if (emptyFlag)
            {
                emptyFlag = false;
                Monitor.PulseAll(list);
            }
            Monitor.Exit(list);
        }

        public T Take(Func<bool> quitHook = null)
        {
            if (quitHook != null && quitHook()) return default;
            Monitor.Enter(list);
            while (list.Count == 0)
            {
                if (quitHook != null && quitHook())
                {
                    Monitor.Exit(list);
                    return default;
                }
                if (!emptyFlag)
                {
                    emptyFlag = true;
                    emptyCallback?.Invoke();
                }
                Monitor.Wait(list, period);
            }
            var v = list[0];
            list.RemoveAt(0);
            if (fullFlag)
            {
                fullFlag = false;
                Monitor.PulseAll(list);
            }
            Monitor.Exit(list);
            return v;
        }
    }
}
